import tkinter as tk
from tkinter import messagebox
import pyaudio
import numpy as np
import librosa
import soundfile as sf
from scipy.signal import butter, filtfilt
import threading

# 初始化 PyAudio
p = pyaudio.PyAudio()

# 音频参数
# 进一步增大 CHUNK 大小，减少缓冲区切换次数
CHUNK = 8192  # 增大 CHUNK 大小
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100

# 全局变量，用于控制变声循环
is_running = False
# 新增：标记流是否已初始化（修复未定义错误）
is_stream_initialized = False  # 关键修复点1
# 线程锁，用于同步音频流的访问
stream_lock = threading.Lock()

# 移除原第18-27行的流预创建代码（修复预创建与动态创建冲突）
# 打开麦克风输入流
with stream_lock:
    stream_in = p.open(format=FORMAT,
                       channels=CHANNELS,
                       rate=RATE,
                       input=True,
                       frames_per_buffer=CHUNK)

# 打开扬声器输出流
with stream_lock:
    stream_out = p.open(format=FORMAT,
                        channels=CHANNELS,
                        rate=RATE,
                        output=True,
                        frames_per_buffer=CHUNK)

# 设计低通滤波器
def butter_lowpass(cutoff, fs, order=5):
    nyquist = 0.5 * fs
    normal_cutoff = cutoff / nyquist
    b, a = butter(order, normal_cutoff, btype='low', analog=False)
    return b, a

def butter_lowpass_filter(data, cutoff, fs, order=5):
    b, a = butter_lowpass(cutoff, fs, order=order)
    y = filtfilt(b, a, data)
    return y

# 变声函数
# 全局变量增加流状态标记
stream_closed = False  # 新增：标记流是否已关闭

# 打开麦克风输入流（修改为延迟创建）
stream_in = None  # 初始化为None
stream_out = None  # 初始化为None

# 停止变声函数（修改后）
def stop_voice():
    global is_running
    is_running = False
    try:
        with stream_lock:
            # 仅停止正在运行的流（不关闭）
            if stream_in and not stream_in.is_stopped():
                stream_in.stop_stream()
            if stream_out and not stream_out.is_stopped():
                stream_out.stop_stream()
    except OSError as e:
        # 忽略已停止的流错误
        if "-9983" not in str(e):  # [Errno -9983] Stream is stopped 是正常停止状态
            print(f"停止流出错: {e}")
    print("变声已暂停")

# 变声函数（核心修复）
def change_voice(pitch_factor=1.0, speed_factor=1.0):
    global is_running, stream_in, stream_out, is_stream_initialized  # 确保引用全局变量
    is_running = True
    print(f"开始变声，音高调节参数: {pitch_factor}，速度调节参数: {speed_factor}")

    try:
        with stream_lock:
            # 首次启动或流未初始化时创建新流（使用已定义的 is_stream_initialized）
            if not is_stream_initialized or not stream_in or not stream_out:
                # 关闭旧流（如果存在）
                if stream_in:
                    stream_in.close()
                if stream_out:
                    stream_out.close()
                # 创建新流
                stream_in = p.open(format=FORMAT,
                                  channels=CHANNELS,
                                  rate=RATE,
                                  input=True,
                                  frames_per_buffer=CHUNK)
                stream_out = p.open(format=FORMAT,
                                   channels=CHANNELS,
                                   rate=RATE,
                                   output=True,
                                   frames_per_buffer=CHUNK)
                is_stream_initialized = True  # 标记初始化完成

            # 启动已停止的流
            if stream_in.is_stopped():
                stream_in.start_stream()
            if stream_out.is_stopped():
                stream_out.start_stream()

        while is_running:
            try:
                with stream_lock:
                    # 检查流是否正常运行
                    if stream_in.is_stopped() or stream_out.is_stopped():
                        break
                    # 读取音频数据（添加溢出容错）
                    data = stream_in.read(CHUNK, exception_on_overflow=False)
                    audio_data = np.frombuffer(data, dtype=np.int16)

                # 转换为浮点数
                audio_data = audio_data.astype(np.float32) / 32767.0

                # 应用低通滤波器
                audio_data = butter_lowpass_filter(audio_data, 10000, RATE)

                # 改变音高，调整n_fft参数
                y_pitched = librosa.effects.pitch_shift(audio_data, sr=RATE, n_steps=pitch_factor * 12, n_fft=CHUNK)

                # 改变速度
                y_stretched = librosa.effects.time_stretch(y_pitched, rate=speed_factor)

                # 转换回整数
                output_data = (y_stretched * 32767).astype(np.int16).tobytes()

                with stream_lock:
                    # 输出音频数据
                    stream_out.write(output_data)
            except Exception as e:
                # 仅处理非流停止的异常
                if "-9983" not in str(e):  # 忽略[Errno -9983] Stream is stopped
                    print(f"音频处理出错: {e}")
                continue

    except Exception as e:
        # 仅记录严重异常（非流关闭错误）
        if "-9988" not in str(e):  # 忽略[Errno -9988] Stream closed
            print(f"发生错误: {e}")
        is_stream_initialized = False  # 异常后标记需要重新初始化
    finally:
        # 仅在主动停止时不强制关闭流
        if not is_running:
            print("变声正常停止")

# 模拟男性音色
def male_voice():
    pitch = -0.5  # 降低音高模拟男性音色
    speed = 1.0
    threading.Thread(target=change_voice, args=(pitch, speed)).start()

# 模拟女性音色
def female_voice():
    pitch = 0.5  # 提高音高模拟女性音色
    speed = 1.0
    threading.Thread(target=change_voice, args=(pitch, speed)).start()

# 创建 GUI 窗口
root = tk.Tk()
root.title("变声器")

# 创建模拟男女音色按钮
male_button = tk.Button(root, text="模拟男性音色", command=male_voice)
male_button.pack()

female_button = tk.Button(root, text="模拟女性音色", command=female_voice)
female_button.pack()

# 创建停止按钮
stop_button = tk.Button(root, text="停止变声", command=stop_voice)
stop_button.pack()

# 运行 GUI 主循环
root.mainloop()